package cn.theten52.demo.patterns.design.creation.singleton;

/**
 * Double Check 形式的单例模式
 *
 * @author wangjin
 * @date 2020/12/25
 */
public class Singleton {

    /**
     * d.为什么要使用 volatile 修饰此变量？
     * <p>
     * 因为singleton == null和Singleton完全构造完成之前有一个短暂的时间差，
     * 多线程环境下不能确保singleton==null判断的原子性，因此在Java SE 1.4及
     * 以前的版本中上面这个Double Check方式不能用。
     * Java SE 5.0之后我们可以用一个volatile关键字修饰静态成员Singleton，确
     * 保多线程访问时，每个线程都必须从共享内存中重读该成员变量的值，而当某个线程
     * 需要对变量修改时，必须将新值回写到共享内存。这样，两个不同的线程读取的
     * singleton == null的结果是一致的。
     * 就是说，当两个线程同时进入到同步代码块时，其中一个获取了锁，并执行了变量初
     * 始化，理论上当另一个线程获取锁后再判断singleton==null应该返回false才
     * 对；但是因为此时两个线程的都各自持有了singleton变量了副本，此时内存中有3
     * 个singleton，即共享内存中的singleton（此时为null），前一个线程的
     * singleton（此时不为null，已经初始化）和后一个线程的singleton（此时为
     * null），故此时后一个线程判断singleton是否为null时使用的还是本线程的变量
     * 副本去判断，得到的结果是singleton仍然为null，还是会再去初始化singleton
     * 一次。所有我们要加上volatile关键字让多个线程读取或者获取此变量时强制从共享
     * 同步变量值。
     */
    private static volatile Singleton singleton = null;

    private Singleton() {
    }

    /**
     * Double Check 形式的单例方法
     *
     * @return Singleton的实例
     */
    public static Singleton getInstance() {
        // c.虽然是多线程环境，但如果没有外层if，客户程序每次执行
        // 时都要先synchronized为Singleton类型，但在绝大多数情
        // 况下，每次都锁定synchronized类型效率太低，这个锁定很
        // 可能就成了整个应用的瓶颈。
        // 一旦唯一的一个实例被创建之后，后续新发起的调用都无须经
        // synchronized部分，直接在外层if判断之后就可获得既有的
        // 唯一实例引用。
        if (singleton == null) {
            // b.使用Singleton.class作为同步对象，
            // 由于jvm保证了类对象的唯一性，因此任何
            // 执行到此处的线程想要继续执行都要获取此
            // Singleton.class对象。
            synchronized (Singleton.class) {
                // a.第二次检查，Double Check 即由此而来。
                // 进入同步代码块后必须还要再检查一次，不然多
                // 线程环境下还是会出现实例化多次的情况。
                // 假设没有此检查，所有进入到此处的线程都初始
                // 化一次Singleton，那么如果在第一个检查处
                // 有两个线程t1、t2同时执行到第一次检查的代码
                // 块，此时需要同步，接下来任一线程获取了锁，执
                // 行到此处创建实例并返回；再接下来另一个线程
                // 也获取锁并又创建实例。那么就会创建两次实例。
                // 此时并不能起到单例的作用；明显，同步代码块的
                // 作用也无关紧要了。
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}
